﻿using Core.DataAccess.ApiJson.SearchService;
using Elasticsearch.Net;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Core.DataAccess.ApiJson.SearchServiceProviders
{
    public class ElasticSearchProvider : ISearchProvider
    {
        ElasticLowLevelClient client;
        public ElasticSearchProvider(ElasticLowLevelClient client)
        {
            this.client = client;
        }

        #region Operation Index
        public (bool IsSuccess, string Result) CreateIndex(string indexName, object indexSchema)
        {
            #region indexSchema Temp
            //new
            //{
            //    mappings = new
            //    {
            //        properties = new
            //        {
            //            name = new
            //            {
            //                type = "text"
            //            },
            //            age = new
            //            {
            //                type = "integer"
            //            },
            //            info = new
            //            {
            //                type = "text"
            //            },
            //            birthDay = new
            //            {
            //                type = "date"
            //            },
            //            location = new
            //            {
            //                type = "geo_point"
            //            }
            //        }
            //    }
            //}
            #endregion
            var response = client.DoRequest<StringResponse>(HttpMethod.PUT, indexName.ToLower(), PostData.Serializable(indexSchema));
            return (response.Success, response.Body);
        }

        public async Task<(bool IsSuccess, string Result)> CreateIndexAsync(string indexName, object indexSchema)
        {
            var response = await client.DoRequestAsync<StringResponse>(HttpMethod.PUT, indexName.ToLower(), default, PostData.Serializable(indexSchema));
            return (response.Success, response.Body);
        }

        public (bool IsSuccess, string Result) AddIndexData(string indexName, IndexData indexData)
        {
            var response = client.Index<StringResponse>(indexName.ToLower(), indexData.Key, PostData.Serializable(indexData.Data));
            return (response.Success, response.Body);
        }

        public async Task<(bool IsSuccess, string Result)> AddIndexDataAsync(string indexName, IndexData indexData)
        {
            var response = await client.IndexAsync<StringResponse>(indexName.ToLower(), indexData.Key, PostData.Serializable(indexData.Data));
            return (response.Success, response.Body);
        }

        public (bool IsSuccess, string Result) ModifyIndex(string indexName, object indexSchema)
        {
            // tabledata/_settings
            // tabledata/_mappings
            var response = client.DoRequest<StringResponse>(HttpMethod.PUT, indexName.ToLower(), PostData.Serializable(indexSchema));
            return (response.Success, response.Body);
        }

        public async Task<(bool IsSuccess, string Result)> ModifyIndexAsync(string indexName, object indexSchema)
        {
            var response = await client.DoRequestAsync<StringResponse>(HttpMethod.PUT, indexName.ToLower(), default, PostData.Serializable(indexSchema));
            return (response.Success, response.Body);
        }

        public (bool IsSuccess, string Result) DeleteIndex(string indexName)
        {
            var response = client.DoRequest<StringResponse>(HttpMethod.DELETE, indexName.ToLower());
            return (response.Success, response.Body);
        }

        public (bool IsSuccess, string Result) AnyIndex(string indexName)
        {
            var response = client.DoRequest<StringResponse>(HttpMethod.PUT, indexName.ToLower() + "/_mappings", PostData.Serializable(new { }));
            return (response.Success, response.Body);
        }

        public async Task<(bool IsSuccess, string Result)> DeleteIndexAsync(string indexName)
        {
            var response = await client.DoRequestAsync<StringResponse>(HttpMethod.DELETE, indexName.ToLower(), default);
            return (response.Success, response.Body);
        }

        public async Task<(bool IsSuccess, string Result)> AnyIndexAsync(string indexName)
        {
            var response = await client.DoRequestAsync<StringResponse>(HttpMethod.PUT, indexName.ToLower() + "/_mappings", default, PostData.Serializable(new { }));
            return (response.Success, response.Body);
        }

        #endregion

        #region Operation Data
        public (bool IsSuccess, string Result) BlukAdditionIndexDatas(string indexName, List<IndexData> datas)
        {
            List<object> requestDatas = new List<object>();
            foreach (var item in datas)
            {
                requestDatas.Add(new { index = new { _index = indexName.ToLower(), _id = item.Key } });
                requestDatas.Add(item.Data);
            }


            //List<object> listData = new List<object>();
            //for (int i = 0; i < datas.Count; i++)
            //{
            //    listData.Add(new { index = new { _index = indexName.ToLower(), _id = datas[i].Key } });
            //    listData.Add(datas[i].Data);
            //}

            var response = client.Bulk<StringResponse>(PostData.MultiJson(requestDatas));
            return (response.Success, response.Body);
        }

        public async Task<(bool IsSuccess, string Result)> BlukAdditionIndexDatasAsync(string indexName, List<IndexData> datas)
        {
            List<object> requestDatas = new List<object>();
            foreach (var item in datas)
            {
                requestDatas.Add(new { index = new { _index = indexName.ToLower(), _id = item.Key } });
                requestDatas.Add(item.Data);
            }

            var response = await client.BulkAsync<StringResponse>(PostData.MultiJson(datas));
            return (response.Success, response.Body);
        }

        public (bool IsSuccess, string Result) DeleteIndexData(string indexName, string indexKey)
        {
            var response = client.Delete<StringResponse>(indexName.ToLower(), indexKey);
            return (response.Success, response.Body);
        }

        public async Task<(bool IsSuccess, string Result)> DeleteIndexDataAsync(string indexName, string indexKey)
        {
            var response = await client.DeleteAsync<StringResponse>(indexName.ToLower(), indexKey);
            return (response.Success, response.Body);
        }

        public (bool IsSuccess, string Result) ModifyIndexData(string indexName, IndexData indexData)
        {
            return this.AddIndexData(indexName, indexData);
        }

        public Task<(bool IsSuccess, string Result)> ModifyIndexDataAsync(string indexName, IndexData indexData)
        {
            return this.AddIndexDataAsync(indexName, indexData);
        }


        #endregion


        public object GetNextPageDataByPrevKey(string key, int minute)
        {
            // 构造查询参数
            var searchParameter = new
            {
                scroll = minute + "m",
                scroll_id = key
            };

            var response = client.DoRequest<StringResponse>(HttpMethod.GET, "/_search/scroll", PostData.Serializable(searchParameter));

            if (response.Success)
            {
                var result = JsonConvert.DeserializeObject<SearchResult>(response.Body);
                result.IsSuccess = response.Success;
                return result;
            }
            else
            {
                return new SearchResult
                {
                    IsSuccess = response.Success,
                    Description = response.Body
                };
            }
        }

        public async Task<object> GetNextPageDataByPrevKeyAsync(string key, int minute)
        {
            // 构造查询参数
            var searchParameter = new
            {
                scroll = minute + "m",
                scroll_id = key
            };

            var response = await client.DoRequestAsync<StringResponse>(HttpMethod.GET, "/_search/scroll", default, PostData.Serializable(searchParameter));

            if (response.Success)
            {
                var result = JsonConvert.DeserializeObject<SearchResult>(response.Body);
                result.IsSuccess = response.Success;
                return result;
            }
            else
            {
                return new SearchResult
                {
                    IsSuccess = response.Success,
                    Description = response.Body
                };
            }
        }

        public object SearchFieldsOfAuto(SearchPagination search, List<SearchCondition> conditions)
        {

            var response = client.DoRequest<StringResponse>(
                HttpMethod.GET,
                $"/{search.IndexName.ToLower()}/_search",
                this.AutoQuery(search, conditions),
                this.GetRequestParameters(search)
               );

            return this.GetSearchResult(response, search);
        }

        public async Task<object> SearchFieldsOfAutoAsync(SearchPagination search, List<SearchCondition> conditions)
        {
            var response = await client.DoRequestAsync<StringResponse>(
                HttpMethod.GET,
                $"/{search.IndexName.ToLower()}/_search",
                default,
                this.AutoQuery(search, conditions),
                this.GetRequestParameters(search)
               );

            return this.GetSearchResult(response, search);
        }

        public object SearchOfLikeContent(SearchPagination search, int fuzziness = 1)
        {
            // 构造查询参数
            var searchParameter = new
            {
                query = this.GetQueryLikeContent(search, fuzziness),
                size = search.Pagination.rows,
                from = this.GetQueryFromInt(search),
                sort = this.GetQueryOrderSort(search),
                _source = this.GetQuerySource(search)
            };

            var response = client.DoRequest<StringResponse>(HttpMethod.GET, $"/{search.IndexName.ToLower()}/_search", PostData.Serializable(searchParameter), this.GetRequestParameters(search));

            return this.GetSearchResult(response, search);
        }

        public async Task<object> SearchOfLikeContentAsync(SearchPagination search, int fuzziness = 1)
        {
            // 构造查询参数
            var searchParameter = new
            {
                query = this.GetQueryLikeContent(search, fuzziness),
                size = search.Pagination.rows,
                from = this.GetQueryFromInt(search),
                sort = this.GetQueryOrderSort(search),
                _source = this.GetQuerySource(search)
            };

            var response = await client.DoRequestAsync<StringResponse>(HttpMethod.GET, $"/{search.IndexName.ToLower()}/_search", default, PostData.Serializable(searchParameter), this.GetRequestParameters(search));

            return this.GetSearchResult(response, search);
        }

        public object SearchOfLocation(SearchPagination search)
        {
            // 构造查询参数
            var searchParameter = new
            {
                query = this.GetQueryGeo(search),
                size = search.Pagination.rows,
                from = this.GetQueryFromInt(search),
                sort = this.GetQueryOrderSort(search),
                _source = this.GetQuerySource(search)
            };

            var response = client.DoRequest<StringResponse>(HttpMethod.GET, $"/{search.IndexName.ToLower()}/_search", PostData.Serializable(searchParameter), this.GetRequestParameters(search));

            return this.GetSearchResult(response, search);
        }

        public async Task<object> SearchOfLocationAsync(SearchPagination search)
        {
            // 构造查询参数
            var searchParameter = new
            {
                query = this.GetQueryGeo(search),
                size = search.Pagination.rows,
                from = this.GetQueryFromInt(search),
                sort = this.GetQueryOrderSort(search),
                _source = this.GetQuerySource(search)
            };

            var response = await client.DoRequestAsync<StringResponse>(HttpMethod.GET, $"/{search.IndexName.ToLower()}/_search", default, PostData.Serializable(searchParameter), this.GetRequestParameters(search));

            return this.GetSearchResult(response, search);
        }

        public object SearchOfRex(SearchPagination search)
        {
            // 构造查询参数
            var searchParameter = new
            {
                query = this.GetQueryRex(search),
                size = search.Pagination.rows,
                from = this.GetQueryFromInt(search),
                sort = this.GetQueryOrderSort(search),
                _source = this.GetQuerySource(search)
            };

            var response = client.DoRequest<StringResponse>(HttpMethod.GET, $"/{search.IndexName.ToLower()}/_search", PostData.Serializable(searchParameter), this.GetRequestParameters(search));

            return this.GetSearchResult(response, search);
        }

        public async Task<object> SearchOfRexAsync(SearchPagination search)
        {
            // 构造查询参数
            var searchParameter = new
            {
                query = this.GetQueryRex(search),
                size = search.Pagination.rows,
                from = this.GetQueryFromInt(search),
                sort = this.GetQueryOrderSort(search),
                _source = this.GetQuerySource(search)
            };

            var response = await client.DoRequestAsync<StringResponse>(HttpMethod.GET, $"/{search.IndexName.ToLower()}/_search", default, PostData.Serializable(searchParameter), this.GetRequestParameters(search));

            return this.GetSearchResult(response, search);
        }

        public object SearchOfWildcard(SearchPagination search)
        {
            // 构造查询参数
            var searchParameter = new
            {
                query = this.GetQueryWildcard(search),
                size = search.Pagination.rows,
                from = this.GetQueryFromInt(search),
                sort = this.GetQueryOrderSort(search),
                _source = this.GetQuerySource(search)
            };

            var response = client.DoRequest<StringResponse>(HttpMethod.GET, $"/{search.IndexName.ToLower()}/_search", PostData.Serializable(searchParameter), this.GetRequestParameters(search));

            return this.GetSearchResult(response, search);
        }

        public async Task<object> SearchOfWildcardAsync(SearchPagination search)
        {

            // 构造查询参数
            var searchParameter = new
            {
                query = this.GetQueryWildcard(search),
                size = search.Pagination.rows,
                from = this.GetQueryFromInt(search),
                sort = this.GetQueryOrderSort(search),
                _source = this.GetQuerySource(search)
            };

            var response = await client
                .DoRequestAsync<StringResponse>(HttpMethod.GET, $"/{search.IndexName.ToLower()}/_search", default, PostData.Serializable(searchParameter), this.GetRequestParameters(search));

            return this.GetSearchResult(response, search);

        }

        public (bool IsSuccess, string Result) GetByKey(string indexName, string indexKey)
        {
            var response = client.Get<StringResponse>(indexName.ToLower(), indexKey);
            return (response.Success, response.Body);
        }

        public async Task<(bool IsSuccess, string Result)> GetByKeyAsync(string indexName, string indexKey)
        {
            var response = await client.GetAsync<StringResponse>(indexName.ToLower(), indexKey);
            return (response.Success, response.Body);
        }


        #region Query Common

        private SearchResult GetSearchResult(StringResponse response, SearchPagination search)
        {
            if (response.Success)
            {
                var result = JsonConvert.DeserializeObject<SearchResult>(response.Body);
                search.Pagination.records = result.Hits.Total.Value;
                search.Pagination.Data = result.Hits.Hits;
                result.IsSuccess = response.Success;
                // result.Description = response.Body;
                return result;
            }
            else
            {
                return new SearchResult
                {
                    IsSuccess = response.Success,
                    Description = response.Body
                };
            }
        }

        /// <summary>
        /// 获取RequestParameters对象
        /// </summary>
        /// <returns></returns>
        private IRequestParameters GetRequestParameters(SearchPagination search)
        {
            IRequestParameters requestParameters = new SearchRequestParameters();

            // 是否上拉加载（不适用传统分页）
            if (search.Pagination.page == -1)
            {
                requestParameters.SetQueryString("scroll", "2m");
            }

            return requestParameters;
        }

        /// <summary>
        /// 构造排序条件
        /// </summary>
        /// <param name="search"></param>
        /// <returns></returns>
        private object GetQueryOrderSort(SearchPagination search)
        {
            // 构造排序
            Dictionary<string, Dictionary<string, string>> sort = default;
            // 按照地址位置排序
            object sortObject = null;

            if (!string.IsNullOrWhiteSpace(search.Pagination.sidx) && search.SortByGeo == null)
            {
                sort = new Dictionary<string, Dictionary<string, string>>();
                sort.Add(search.Pagination.sidx, new Dictionary<string, string>
                {
                    { "order",search.Pagination.sord.ToLower() == "desc" ? "desc" : "asc" }
                });
                return sort;
            }

            // 按照地址位置排序
            else if (search.SortByGeo != null && !string.IsNullOrWhiteSpace(search.Pagination.sidx))
            {
                Dictionary<string, object> _geo_distance_dic = new Dictionary<string, object>();

                _geo_distance_dic.Add(search.Pagination.sidx, new
                {
                    search.SortByGeo.geo.lat,
                    search.SortByGeo.geo.lon
                });

                _geo_distance_dic.Add("order", search.Pagination.sord);
                _geo_distance_dic.Add("unit", search.SortByGeo.unit);

                sortObject = new
                {
                    _geo_distance = _geo_distance_dic
                };
            }

            return sortObject;
        }

        /// <summary>
        /// 获取From数据
        /// </summary>
        /// <param name="search"></param>
        /// <returns></returns>
        private int GetQueryFromInt(SearchPagination search)
        {
            int page = search.Pagination.page < 1 ? 1 : search.Pagination.page;
            return (page - 1) * search.Pagination.rows;
        }

        /// <summary>
        /// 获取查询字段
        /// </summary>
        /// <param name="search"></param>
        /// <returns></returns>
        private object GetQuerySource(SearchPagination search)
        {
            // 构造查询字段
            object _source = string.IsNullOrWhiteSpace(search.Column) ? "*" : (search.Column == "*") ? "*" : string.Empty;

            if (search.Column == "`")
            {
                _source = string.Empty;
            }
            else
            {
                _source = _source.Equals(string.Empty) ? search.Column?.Split(",") ?? _source : _source;
            }

            return _source;
        }
        #endregion

        #region Query Type

        /// <summary>
        /// Wildcard
        /// </summary>
        /// <returns></returns>
        private object GetQueryWildcard(SearchPagination search)
        {
            return this.GetQueryWildcard(search.SearchField, search.Value);
        }

        private object GetQueryWildcard(string field, object value)
        {
            return new
            {
                wildcard = new Dictionary<string, object>() { { field, value } },
            };
        }

        /// <summary>
        /// Rex
        /// </summary>
        /// <param name="search"></param>
        /// <returns></returns>
        private object GetQueryRex(SearchPagination search)
        {
            return this.GetQueryRex(search.SearchField, search.Value);
        }

        private object GetQueryRex(string field, object value)
        {
            return new
            {
                regexp = new Dictionary<string, object>() { { field, value } },
            };
        }

        /// <summary>
        /// 根据地址坐标搜索
        /// </summary>
        /// <param name="search"></param>
        /// <returns></returns>
        private object GetQueryGeo(SearchPagination search)
        {
            return this.GetQueryGeo(search.SearchField, search.Value + search.SortByGeo.unit, search.SortByGeo.geo);
        }

        private object GetQueryGeo(string field, string distance, Geo geo)
        {
            var geo_distance = new Dictionary<string, object>();
            if (!string.IsNullOrWhiteSpace(distance))
            {
                geo_distance.Add("distance", distance);
            }
            geo_distance.Add(field, new
            {
                geo.lat,
                geo.lon
            });

            return new
            {
                constant_score = new
                {
                    filter = new
                    {
                        geo_distance = geo_distance
                    },
                    boost = 1.2
                }
            };
        }

        private object GetQueryLikeContent(SearchPagination search, int fuzziness)
        {
            return this.GetQueryLikeContent(search.SearchField, search.Value, fuzziness);
        }

        private object GetQueryLikeContent(string field, object value, int fuzziness)
        {
            if (value.GetType().IsValueType)
            {
                Dictionary<string, object> dic = new Dictionary<string, object>();
                dic.Add(field, value);
                return new
                {
                    match = dic
                };
            }
            else
            {
                return new
                {
                    multi_match = new
                    {
                        query = value,
                        fields = new string[] { field },
                        fuzziness = fuzziness
                    }
                };
            }

        }

        private string AutoQuery(SearchPagination search, List<SearchCondition> conditions)
        {
            // 忽略NULL字段
            var jsonSetting = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
            var json = JsonConvert.SerializeObject(new
            {
                query = new
                {
                    @bool = "$1"
                },
                size = search.Pagination.rows,
                from = this.GetQueryFromInt(search),
                sort = this.GetQueryOrderSort(search),
                _source = this.GetQuerySource(search)
            }, jsonSetting);

            List<string> querys = new List<string>();

            foreach (var item in conditions)
            {
                object condition = null;

                switch (item.SearchType)
                {
                    case ConditionEnum.Like:
                        condition = this.GetQueryLikeContent(item.SearchField, item.Value, 1);
                        break;
                    case ConditionEnum.Geo:
                        condition = this.GetQueryGeo(item.SearchField, item.Value + search.SortByGeo.unit, item.Geo);
                        break;
                    case ConditionEnum.Rex:
                        condition = this.GetQueryRex(item.SearchField, item.Value);
                        break;
                    case ConditionEnum.Wildcard:
                        condition = this.GetQueryWildcard(item.SearchField, item.Value);
                        break;
                }

                querys.Add(JsonConvert.SerializeObject(condition));
            }

            return json.Replace("{\"bool\":\"$1\"", $"{{\"bool\":{{\"should\":[{string.Join(',', querys)}],\"minimum_should_match\":{conditions.Count}}}");
        }
        #endregion
    }
}
